/**
 *  Copyright (C) 2013  Piotr Szczepański
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package edu.pw.elka.gtsna.weka_interface;

import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;

import edu.pw.elka.gtsna.interaction_index.InteractionIndex;
import edu.pw.elka.gtsna.interaction_index.kStepsDegreeII;
import edu.pw.elka.gtsna.interaction_index.NaiveII;

import weka.core.DenseInstance;
import weka.core.DistanceFunction;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.Option;
import weka.core.neighboursearch.PerformanceStats;

/**
 * @author Aleksy Barcz
 */
public class NodeDistance implements DistanceFunction {
	
	
	protected NodeInstances instances;
	protected int stepsNumber;
	protected boolean initialized;
	protected InteractionIndex interactionIndex;
	protected boolean showWarnings = true;
	protected String interactionIndexType;
	
	
	public NodeDistance(Instances instances, int stepsNumber, String iiType) throws Exception {
		if (!iiType.equals("NAIVE") && !iiType.equals("KSTEPS")) {
			throw new Exception("Unknown interactionIndexType: " + iiType);
		}
		interactionIndexType = iiType;
		setInstances(instances);
		setStepsNumber(stepsNumber);
	}

	/* (non-Javadoc)
	 * @see weka.core.OptionHandler#listOptions()
	 */
	@Override
	public Enumeration<Option> listOptions() {
		// TODO Auto-generated method stub
		return null;
	}

	/* (non-Javadoc)
	 * @see weka.core.OptionHandler#setOptions(java.lang.String[])
	 */
	@Override
	public void setOptions(String[] options) throws Exception {
		// TODO Auto-generated method stub

	}

	/* (non-Javadoc)
	 * @see weka.core.OptionHandler#getOptions()
	 */
	@Override
	public String[] getOptions() {
		// TODO Auto-generated method stub
		return null;
	}

	/* (non-Javadoc)
	 * @see weka.core.DistanceFunction#setInstances(weka.core.Instances)
	 */
	@Override
	public void setInstances(Instances insts) {
		instances = (NodeInstances) insts;	// ClassCastException if not NodeInstances
		initialized = false;
	}
	
	public void setStepsNumber(int number) {
		stepsNumber = number;
		initialized = false;
	}
	
	public boolean isInitialized() {
		return initialized;
	}

	/* (non-Javadoc)
	 * @see weka.core.DistanceFunction#getInstances()
	 */
	@Override
	public Instances getInstances() {
		return instances;
	}

	/* (non-Javadoc)
	 * @see weka.core.DistanceFunction#setAttributeIndices(java.lang.String)
	 */
	@Override
	public void setAttributeIndices(String value) {
		// TODO Auto-generated method stub

	}

	/* (non-Javadoc)
	 * @see weka.core.DistanceFunction#getAttributeIndices()
	 */
	@Override
	public String getAttributeIndices() {
		return "1";
	}

	/* (non-Javadoc)
	 * @see weka.core.DistanceFunction#setInvertSelection(boolean)
	 */
	@Override
	public void setInvertSelection(boolean value) {
		// TODO Auto-generated method stub

	}

	/* (non-Javadoc)
	 * @see weka.core.DistanceFunction#getInvertSelection()
	 */
	@Override
	public boolean getInvertSelection() {
		// TODO Auto-generated method stub
		return false;
	}

	/* (non-Javadoc)
	 * @see weka.core.DistanceFunction#distance(weka.core.Instance, weka.core.Instance)
	 */
	@Override
	public double distance(Instance first, Instance second) {
		precomputeDistances();
		int firstNode = getNode((DenseInstance) first);
		int secondNode = getNode((DenseInstance) second);
		double value = posValue - interactionIndex.interaction(firstNode, secondNode);
		if (showWarnings && value < 0) {
			System.out.println("ERROR in NodeDistance, distance < 0. The clustering results will be incorrect.");
		}
		//System.out.format("Distance between %d and %d is %f\n", firstNode, secondNode, value);
		return value;
	}
	
	public void hideWarnings() {
		showWarnings = false;
	}
	
	public void showWarnings() {
		showWarnings = true;
	}
	
	public void setPosValue(double pos) {
		posValue = pos;
	}
	
	protected double posValue = 0;
	
	public double distance(int first, int second) {
		precomputeDistances();
		return interactionIndex.interaction(first, second);
	}
	
	public double distance(Set<Instance> first, Set<Instance> second) {
		precomputeDistances();
		return interactionIndex.interaction(instancesToNodes(first), instancesToNodes(second));
	}
	
	public Set<Integer> instancesToNodes(Set<Instance> instances) {
		Set<Integer> indices = new HashSet<Integer>();
		for (Instance instance: instances) {
			indices.add(getNode(((DenseInstance) instance)));
		}
		return indices;
	}
	
	protected void precomputeDistances() {
		if (!initialized) {
			if (interactionIndexType.equals("NAIVE")) {
				interactionIndex = new NaiveII(instances.getGraph(), stepsNumber);
			} else if (interactionIndexType.equals("KSTEPS")) {
				interactionIndex = new kStepsDegreeII(instances.getGraph(), stepsNumber);
			}
			interactionIndex.preComputation();
			initialized = true;
		}
	}
	
	protected int getNode(DenseInstance instance) {
		return (int) Math.round(instance.value(0));
	}

	/* (non-Javadoc)
	 * @see weka.core.DistanceFunction#distance(weka.core.Instance, weka.core.Instance, weka.core.neighboursearch.PerformanceStats)
	 */
	@Override
	public double distance(Instance first, Instance second,
			PerformanceStats stats) throws Exception {
		return distance(first, second);
	}

	/* (non-Javadoc)
	 * @see weka.core.DistanceFunction#distance(weka.core.Instance, weka.core.Instance, double)
	 */
	@Override
	public double distance(Instance first, Instance second, double cutOffValue) {
		return distance(first, second);
	}

	/* (non-Javadoc)
	 * @see weka.core.DistanceFunction#distance(weka.core.Instance, weka.core.Instance, double, weka.core.neighboursearch.PerformanceStats)
	 */
	@Override
	public double distance(Instance first, Instance second, double cutOffValue,
			PerformanceStats stats) {
		return distance(first, second);
	}

	/* (non-Javadoc)
	 * @see weka.core.DistanceFunction#postProcessDistances(double[])
	 */
	@Override
	public void postProcessDistances(double[] distances) {
		// TODO Auto-generated method stub

	}

	/* (non-Javadoc)
	 * @see weka.core.DistanceFunction#update(weka.core.Instance)
	 */
	@Override
	public void update(Instance ins) {
		// TODO Auto-generated method stub

	}



}
